package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	/*
		1.sync.Mutex 和 sync.RWMutex
			在处理 goroutine 时，确保它们不会同时访问资源是非常重要的，而 mutex 可以帮助我们做到这一点

		（1）sync.Mutex
			互斥锁用来保证任一时刻，只能有一个线程函数访问某对象，保证共享数据的安全性防止竞态。
			Mutex 通常作为其他结构体的匿名字段使用，使该结构体具有 Lock 和 Unlock 方法。
			Mutex 可以安全的在多个例程中并行并使用

		（2）为什么使用 sync.RWMutex？
			// Lock 将 rw 设置为写锁定状态，禁止其他例程读取或写入。
			func (rw *RWMutex) Lock()

			// Unlock 解除 rw 的写锁定状态，如果 rw 未被写锁定，则该操作会引发 panic。
			func (rw *RWMutex) Unlock()

			// RLock 将 rw 设置为读锁定状态，禁止其他例程写入，但可以读取。
			func (rw *RWMutex) RLock()

			// Runlock 解除 rw 的读锁定状态，如果 rw 未被读锁顶，则该操作会引发 panic。
			func (rw *RWMutex) RUnlock()

			// RLocker 返回一个互斥锁，将 rw.RLock 和 rw.RUnlock 封装成了一个 Locker 接口。
			func (rw *RWMutex) RLocker() Locker

			假如我们正在检查 a 变量，但其他 goroutine 也在调整它。我们就可能会得到过时的信息。那么，解决这个问题的方法是什么？
			1、如果我们使用 sync.Mutex 方法，将 sync.Mute 添加到我们的 Get() 函数
				func Add() {
					mtx.Lock()
					defer mtx.Unlock()
					a++
				}
				func Get() int {
					mtx.Lock()
					defer mtx.Unlock()
					return a
				}
			问题是，如果我们的服务或程序调用 Get() 数百万次而只调用 Add() 几次，那么我们实际上是在浪费资源，因为我们大部分时间甚至都没有修改它而将所有内容都锁定了
			而 sync.RWMutex 就是来帮我们处理同时读取和写入的情况。
				var mtx = sync.RWMutex{}
				func Add() {
				  mtx.Lock()
				  defer mtx.Unlock()
				  a++
				}
				func Look() {
				  mtx.RLock()
				  defer mtx.RUnlock()
				  fmt.Println(a)
				}
			RWMutex 允许数百万次并发读取，同时确保一次只能进行一次写入。
				写入时，读取被锁定
				读取时，写入被锁定
				多次读取不会相互锁定
			1、当读写锁是写加锁状态时，在这个锁被解锁之前，所有试图对这个锁加锁的线程都会被阻塞。
			2、当读写锁在加锁状态时，所有试图以读写模式对它进行加锁的线程都可以得到访问权
			3、通常, 当读写锁处于读模式锁住状态时, 如果有另外线程试图以写模式加锁,
			读写锁通常会阻塞随后的读模式锁请求, 这样可以避免读模式锁长期占用, 而等待的写模式锁请求长期阻塞。

		实用场景：读写锁适用于对数据结构的读次数比写次数多得多的情况。
			因为, ”读模式锁定时可以共享, 以写模式锁住时意味着独占“, 所以读写锁又叫共享-独占锁。

		（3）sync.Locker
			Mutex 和 RWMutex 都实现了 sync.Locker{} 接口
				type Locker interface {
					Lock()
					Unlock()
				}
				// Lock 用于锁住 m，如果 m 已经被加锁，则 Lock 将被阻塞，直到 m 被解锁。
				func (m *Mutex) Lock()

				// Unlock 用于解锁 m，如果 m 未加锁，则该操作会引发 panic。
				func (m *Mutex) Unlock()

			如果我们想创建一个接受 Locker 的函数，我们可以将这个函数与我们自定义 Locker 或同步互斥锁一起使用；
				func Add(mtx sync.Locker) {
					mtx.Lock()
					defer mtx.Unlock()
					a++
				}

		2.sync.WaitGroup
			组等待，用于等待一组例程的结束。主例程在创建每个子例程时，先调用 Add 增加等待计数，每个子例程在结束时调用 Done 减少例程计数。
			之后，主例程通过 Wait 方法开始等待，直到计数器归零才继续执行。

			sync.WaitGroup 有 3 个主要方法：Add、Done、Wait。
			（1）func (wg *WaitGroup) Add(delta int):
				将 WaitGroup 计数器增加 delta 的值。我们通常会在生成 goroutine 之前调用它，表示有一个额外的任务需要完成。
				如果我们将 Add 函数放在 go func(){} 中，会引发 panic
			（2）func (wg *WaitGroup) Done():
				相当于 Add(-1)，当一个 goroutine 结束它的任务时，Done 被调用。
			（3）func (wg *WaitGroup) Wait()
				Wait 会阻塞调用者，直到 WaitGroup 计数器归零，这意味着所有派生的 goroutine 都已经完成它们的任务。

			☀ 如果并发协程之间不需要消息传递，非常适合 sync.WaitGroup

		3.sync.Once
			Once 的作用是多次调用但只执行一次，
			Once 只有一个方法，Once.Do()，向 Do 传入一个函数，这个函数在第一次执行 Once.Do() 的时候会被调用，
			以后再执行 Once.Do() 将没有任何动作，即使传入了其它的函数，也不会被执行，
			如果要执行其它函数，需要重新创建一个 Once 对象。Once 可以安全的在多个例程中并行使用。

			假设我们在一个包中有一个 CreateInstance() 函数，但我们需要确保它在使用前已经初始化。所以我们所在的不同的地方多次调用它，像这样：
				var i = 0
				var _isInitialized = false
				func CreateInstance() {
					if _isInitialized {
						return
					}
					i = GetISomewhere()
					_isInitialized = true
				}
			但是如果有多个 goroutine 调用这个方法，i = GetISomewhere 行会运行多次，即使我们为了稳定性只希望它执行一次。

			可以使用互斥锁，但也可以使用 sync.Once 方法：
				var i = 0
				var once = &sync.Once{}
				func CreateInstance() {
					once.Do(func() {
						i = GetISomewhere()
					})
				}
			使用 sync.Once，我们可以确保一个函数只执行一次，不管它被调用了多少次或者有多少 goroutine 同时调用它。

		4.sync.Pool
			假设我们有一个池，里面有一堆我们想反复使用的对象。这可以减轻垃圾收集器的一些压力，尤其是在创建和销毁这些资源的成本很高的情况下。
			所以，无论何时我们需要一个对象，我们都可以从池中取出它。当我们使用完它时，我们可以将它放回池中以备日后重复使用。
		注：
			将对象放入池中的顺序不一定是它们出来的顺序，即使多次运行上述代码时顺序也是随机。
		sync.Pool 的使用技巧：
		（1）它非常适合长期存在并且有多个实例需要管理的对象，例如数据库连接、worker goroutine、甚至 缓冲区。
		（2）在将对象返回池之前始终重置对象的状态。这样，就可以避免任何无意间的数据泄露或奇怪的行为。
		（3）不要指望池中已经存在的对象，因为它们可能会意外释放

		5.Map 类型
			跟 Go 中的 map[interface{}]interface{} 类似，但是对于多个 goroutine 并发使用而不需要额外的锁定或同步是安全的。
			Map类型是定制的，主要是保证单独使用时保证并发执行的类型安全性。

		Map 类型针对两种常见使用情况进行了优化：
		（1）给定键的入口只写入一次，但多次读取，仅仅在缓存中存在
		（2）在多个 goroutine 读取、写入 和 覆盖 不相交键集的条目。
		在这两种情况下，与使用单独互斥或 RWMutex 锁配对的 Go类型相比，使用 Map类型可以显著减少锁竞争。

		··当我们同时使用 map 时，有点像使用 RWMutex。我们可以同时进行多次读取，但不能进行多次读写或写入。
		如果存在冲突，我们的服务将 崩溃 而不是 覆盖数据 或 导致意外行为。

		··这时，sync.Map 就可以帮助我们避免这种问题。
			func (m *Map) LoadOrStore(key, value any) (actual any, loaded bool) :
				获取当前键值或保存并返回提供的值（如果不存在）。
			func (m *Map) Store(key, value any) :
				设置一个键的值。
			func (m *Map) Load(key any) (value any, ok bool) :
				返回存储在映射中的键的值，
				如果没有值，则返回 nil；
				ok 结果表明 是否在映射中找到了值。
			func (m *Map) Delete(key any) :
				删除键的值
			func (m *Map) LoadAndDelete(key any) (value any, loaded bool) :
				删除一个键的值，返回以前的值（如果有的话）。
				loaded 结果表明该键是否存在。
			func (m *Map) Range(f func(key, value any) bool) :
				遍历所有键值对，即使 f 在恒定数量的调用后返回 false，Range 也可以是 O(N)与映射中的元素数。

		··我们为什么不使用带有 Mutex 的常规 map 呢？
			我们通常选择带有 RWMutex 的 map，但在某些情况下，认识到 sync.Map 的强大功能很重要。
			如果我们有许多 goroutine 访问 map 中的单独键，则具有单个互斥锁的常规 map 可能会导致争用，因为它仅针对单个写操作锁定 map。
			而 sync.Map 使用更完善的锁定机制，有助于最大限度的减少此类场景中的争用。

		6.sync.Cond
			（1）将 sync.Cond 视为支持多个 goroutine 等待和相互交互的条件变量。
			（2）条件等待当条件不满足时通过 Wait 函数让一个例程等待，
				另一个例程改变条件并通过 Signal 发送信号让 一个等待 的例程继续，通过 Broadcast 让 所有等待 的例程继续。

			（3）在 Wait 之前应当手动为 c.L 上锁，Wait 结束后手动解锁。为避免虚假唤醒，需要将 Wait 放到一个条件判断循环中。
				c.L.Lock()
				for !condition() {
					c.Wait()
				}
				//执行条件满足之后的动作。。。
				c.L.Unlock()

			（4）type Cond struct {
					L Locker // 在“检查条件”或“更改条件”时 L 应该锁定。
				}

				// 创建一个条件等待    初始化互斥锁变量，返回Cond变量
				func NewCond(l Locker) *Cond

				// Broadcast 唤醒 所有等待 的 Wait，建议在“更改条件”时锁定 c.L，更改完毕再解锁。
				func (c *Cond) Broadcast()

				// Signal 唤醒 一个等待 的 Wait，建议在“更改条件”时锁定 c.L，更改完毕再解锁。
				func (c *Cond) Signal()

				// Wait 会解锁 c.L 并进入等待状态，在被唤醒时，会重新锁定 c.L
				func (c *Cond) Wait()

			（5）创建带有 Locker 的 sync.Cond:
					var mtx sync.Mutex
					var cond = sync.NewCond(&mtx)
				goroutine 调用 cond.Wait 并等待来自其他地方的信号以继续执行：
					func dummyGoroutine(id int) {
						cond.L.Lock()
						defer cond.L.Unlock()
						fmt.Printf("Goroutine %d is waiting ...\n", id)
						cond.Wait()
						fmt.Printf("Goroutine %d received the signal.\n", id)
					}
				然后，另一个 goroutine（就像goroutine）调用 cond.Signal()，让我们等待的 goroutine 继续：
					func main() {
						go dummyGoroutine(1)
						time.Sleep(1*time.Second)
						fmt.Println("Sending signal...")
						cond.Signal()
						time.Sleep(1*time.Second)
					}

				如果有多个 goroutine 在等待我们的信号怎么办？这就是我们可以使用广播的时候：
					func main() {
						go dummyGoroutine(1)
						go dummyGoroutine(2)

						time.Sleep(1 * time.Second)
						cond.Broadcast() //向所有的 goroutine广播
						time.Sleep(1 * time.Second)
					}

	*/
	fmt.Println("---------------------------sync.Mutex-------------------------")
	/*
		没有使用互斥锁来保护我们的变量a，其结果是不可预测的。如果幸运的话，您可能会得到 500，但通常结果会小于 500。
		使用互斥体增强我们的 Add 函数后，就可以解决这个问题
	*/
	for i := 0; i < 500; i++ {
		go Add1()
		//go func() {
		//	fmt.Println(Get())
		//}()
		Look()
	}
	time.Sleep(5 * time.Second)
	fmt.Println(a)
	x := sync.WaitGroup{}
	x.Wait()

	fmt.Println("-----------------------pool------------------------")

	pool.Put(1)
	pool.Put(2)
	pool.Put(3)
	a := pool.Get().(int)
	b := pool.Get().(int)
	c := pool.Get().(int)
	fmt.Println(a, b, c) // 1 3 2 (order may vary)

	fmt.Println("----------------------------sync.Cond--------------------------------")

	go dummyGoroutine(1)
	time.Sleep(1 * time.Second)
	fmt.Println("Sending signal...")
	cond.Signal()
	time.Sleep(1 * time.Second)
	/*
		Goroutine 1 is waiting...
		Sending signal...
		Goroutine 1 received the signal.
	*/

	go dummyGoroutine(1)
	go dummyGoroutine(2)

	time.Sleep(1 * time.Second)
	cond.Broadcast() //向所有的 goroutine广播
	time.Sleep(1 * time.Second)
	/*
		Goroutine 1 is waiting...
		Goroutine 2 is waiting...
		Goroutine 2 received the signal.
		Goroutine 1 received the signal.
	*/

	fmt.Println("----------------------------------sync.Cond标准写法------------------------------")

	condition := false //条件不满足
	var mu sync.Mutex  //锁 1、保护条件变量 2、保护等待队列
	cond := sync.NewCond(&mu)
	//让例程去创造条件
	go func() {
		mu.Lock()
		condition = true //更改条件
		cond.Signal()    //发送通知：条件已满足
		mu.Unlock()
	}()
	mu.Lock()
	//检查条件是否满足，避免虚假通知，同时避免 Signal 提前于 Wait 执行。
	for !condition {
		//等待条件满足的通知，如果收到虚假通知，则循环继续等待。 =====这里 Wait 函数的实现会有解锁操作，等待唤醒等待队列中的线程
		cond.Wait() //等待时 mu 处于解锁状态，唤醒时重新锁定。
		//Wait 函数实现类似于 Linux 条件变量
	}
	fmt.Println("条件满足，开始后续动作。。。")
	mu.Unlock()
}

var pool = sync.Pool{
	New: func() interface{} {
		return 0
	},
}

var a = 0

//func Add() {
//	a = a + 1
//}

var mtx = sync.Mutex{}

// 使用互斥体加强的Add函数
func Add() {
	mtx.Lock()
	defer mtx.Unlock()
	a++
}
func Get() int {
	mtx.Lock()
	defer mtx.Unlock()
	return a
}

// sync.RWMutex
var mtx1 = sync.RWMutex{}

func Add1() {
	mtx1.Lock()
	defer mtx1.Unlock()
	a++
}
func Look() {
	mtx1.RLock()
	defer mtx1.RUnlock()
	fmt.Println(a)
}

// sync.Cond
var mtx3 sync.Mutex
var cond = sync.NewCond(&mtx3)

func dummyGoroutine(id int) {
	cond.L.Lock()
	defer cond.L.Unlock()
	fmt.Printf("Goroutine %d is waiting...\n", id)
	cond.Wait()
	fmt.Printf("Goroutine %d received the signal.\n", id)
}
